
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/evp.h>
#include <openssl/engine.h>
#include <openssl/conf.h>
#include "debug.h"
#include "SSLInterface.h"

#if APR_HAS_THREADS
apr_pool_t *ssl_pool;
apr_thread_mutex_t **ssl_locks;

typedef struct CRYPTO_dynlock_value
{
   apr_thread_mutex_t *lock;
} CRYPTO_dynlock_value;

static CRYPTO_dynlock_value *ssl_dyn_create(const char *file, int line)
{
   CRYPTO_dynlock_value *l;
   apr_status_t rv;

   l = apr_palloc(ssl_pool, sizeof(CRYPTO_dynlock_value));
   rv = apr_thread_mutex_create(&(l->lock), APR_THREAD_MUTEX_DEFAULT, ssl_pool);
   if (rv != APR_SUCCESS)
   {
      errprint("failed to create mutex for dynamic lock");
      return NULL;
   }
   return l;
}

static void ssl_dyn_lock(int mode, CRYPTO_dynlock_value *l, const char *file, int line)
{
   if (mode & CRYPTO_LOCK)
      apr_thread_mutex_lock(l->lock);
   else
      apr_thread_mutex_unlock(l->lock);
}

static void ssl_dyn_destroy(CRYPTO_dynlock_value *l, const char *file, int line)
{
   apr_thread_mutex_destroy(l->lock);
}

static void ssl_lock(int mode, int n, const char *file, int line)
{
   if (mode & CRYPTO_LOCK)
      apr_thread_mutex_lock(ssl_locks[n]);
   else
      apr_thread_mutex_unlock(ssl_locks[n]);
}

static unsigned long ssl_id(void)
{
#ifdef __MVS__
   struct PSA {
      char unmapped[540];
      unsigned long PSATOLD;
   } *psaptr = 0;

   return psaptr->PSATOLD;
#else
   return (unsigned long) apr_os_thread_current();
#endif
}

static apr_status_t cleanup_ssl(void *data)
{
   CRYPTO_set_locking_callback(NULL);
   CRYPTO_set_id_callback(NULL);
   CRYPTO_set_dynlock_create_callback(NULL);
   CRYPTO_set_dynlock_lock_callback(NULL);
   CRYPTO_set_dynlock_destroy_callback(NULL);

   CONF_modules_unload(1);
   EVP_cleanup();
   ENGINE_cleanup();
   CRYPTO_cleanup_all_ex_data();
   ERR_remove_state(0);
   ERR_free_strings();
   return APR_SUCCESS;
}

#endif

static int have_init_ssl = 0;

static void init_ssl_libraries(void)
{
   if (!have_init_ssl)
   {
#if APR_HAS_THREADS
      int i, numlocks;
#endif
      CRYPTO_malloc_init();
      ERR_load_crypto_strings();
      SSL_load_error_strings();
      SSL_library_init();
      OpenSSL_add_all_algorithms();

#if APR_HAS_THREADS
      numlocks = CRYPTO_num_locks();
      apr_pool_create(&ssl_pool, NULL);
      ssl_locks = apr_palloc(ssl_pool, sizeof(apr_thread_mutex_t*)*numlocks);
      for (i = 0; i < numlocks; i++) 
      {
         apr_status_t rv;

         rv = apr_thread_mutex_create(&ssl_locks[i], APR_THREAD_MUTEX_DEFAULT, ssl_pool);
         if (rv != APR_SUCCESS)
         {
            errprint("could not create mutex %d of %d", i, numlocks);
            return;
         }
      }
      CRYPTO_set_locking_callback(ssl_lock);
      CRYPTO_set_id_callback(ssl_id);
      CRYPTO_set_dynlock_create_callback(ssl_dyn_create);
      CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock);
      CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy);

      apr_pool_cleanup_register(ssl_pool, NULL, cleanup_ssl, cleanup_ssl);
#endif
      have_init_ssl = 1;
   }
}

SSLInterface::SSLInterface()
{
   init_ssl_libraries();
}

SSLInterface::~SSLInterface()
{
/*   apr_status_t status;
   status = cleanup_ssl(NULL);
   CheckAPRError(status);
   */

   // This is unsafe - cleaning OpenSSL? - https://github.com/roddi/ValidateStoreReceipt/issues/15
}

void SSLInterface::CheckSSLError(const char *fpretty, int line)
{
   // According to the documentation, this is what it wants...
   char static_errbuf[120];
   unsigned long errvalue = ERR_get_error();
   while (errvalue != 0)
   {
      ERR_error_string_n(ERR_get_error(), static_errbuf, 120);
      errprintpd(static_errbuf, fpretty, line);
      errvalue = ERR_get_error();
   }
}
